﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using KEUSBModule;
using Descriptions_DomainModel.Model;
using Descriptions_PinsDescription.Model;
using CommonClasses;
using System.Windows.Forms;
using VMLua;
using CommonClasses.Utility;
using Eco.ObjectRepresentation;
using VisualDM.Model;
using RunTime.Properties;
using CommonClasses.Services;
using PinsDescriptionEditor;
using Eco.Services;
using Axiom.Math;
using Axiom.Core;
using CommonClasses.Logger;
using CommonClasses.Model;
using ModellingSupervisor;

namespace RunTime
{

    /// <summary>
    /// Тип состояния исполнения
    /// </summary>
    public enum StateType
    {
        Run, Pause, Stop
    }


    /// <summary>
    /// Менеджер, запускающий исполнение на установке и управляющий им
    /// </summary>
    public class RunTimeManager
    {

        /// <summary>
        /// Интервал системного таймера
        /// </summary>
        private const int SYS_TIMER_INTERVAL = 40;

        #region Public Events

        /// <summary>
        /// Результат исполнения
        /// </summary>
        public enum RunFinshedState
        {
            Successfull, ErrorConnecting
        }


        /// <summary>
        /// Обработчик события завершения исполнения
        /// </summary>
        /// <param name="runFinishedState">результат исполнения</param>
        public delegate void RunTimeFinshedEventHandler(RunFinshedState runFinishedState);


        /// <summary>
        /// Событие: исполнение закончено
        /// </summary>
        /// <remarks> 
        /// вызывается каждый раз по завершению исполнения
        /// </remarks>
        public event RunTimeFinshedEventHandler FinishedEvent;


        /// <summary>
        /// Вызвать событие окончания исполнения
        /// </summary>
        /// <param name="runFinishedState">результат исполнения</param>
        public void Finished(RunFinshedState runFinishedState)
        {
            if (FinishedEvent != null)
                FinishedEvent(runFinishedState);
        }
        #endregion

        #region Private Fields

        /// <summary>
        /// Менеджер
        /// </summary>
        /// <remarks>
        /// Реализация паттерна проектирования 'singleton'
        /// </remarks>
        private static RunTimeManager manager;

        /// <summary>
        /// Сервисы ECO
        /// </summary>
        private IEcoServiceProvider serviceProvider;

        /// <summary>
        /// Сервис Ocl
        /// </summary>
        private IOclService oclService;


        /// <summary>
        /// Менеджмент логгеров
        /// </summary>
        private ILoggingManagement loggingManagement;

        /// <summary>
        /// Логгер
        /// </summary>
        private ILogger logger;


        /// <summary>
        /// Фильтр сообщений, отлавливающий нажатия на клавишу
        /// </summary>
        /// <remarks>
        /// Вызывает всех, кто у него подписался.
        /// Cам предварительно регистрируется у Application на нажатие клавиши
        /// </remarks>
        private KeyPressMessageFilter keyFilter;


        /// <summary>
        /// Описание оборудования
        /// </summary>
        private DomainModel domainModel;

        /// <summary>
        /// Описание оборудования для визуализатора
        /// </summary>
        private VisualDomainModel visualDomainModel;

        /// <summary>
        /// Таблица описания пинов
        /// </summary>
        private PinsDescriptionTable pinsDescriptionTable;


        /// <summary>
        /// Таймер, по которому будет проиводиться обновление кадров визуализатора
        /// </summary>
        private System.Windows.Forms.Timer visualizationTimer;

        /// <summary>
        /// Контрол, на котором производится визуализация
        /// </summary>
        private Control visualizationTarget;

        /// <summary>
        /// Контрол, требующий фокуса 
        /// </summary>
        /// <remarks>
        /// чтобы Axiom не перехватывал его полностью себе
        /// </remarks>
        private Control appControl;

        /// <summary>
        /// Визуализатор
        /// </summary>
        public Visualizator.Visualizator Visualizator;

        /// <summary>
        /// Настроена ли визуализация (она не может быть настроена при отсутствии преокта)
        /// </summary>
        private bool VisualisationInitislized = false;


        /// <summary>
        /// Удалось ли установить связь с установкой
        /// </summary>
        public bool IsRealBenchConnected = false;

        /// <summary>
        /// Список модулей для связи с устройствами через порты
        /// </summary>
        /// <remarks> список нужен лишь для того, чтобы потом их отключить</remarks>
        private List<IModule> modules_real = new List<IModule>();

        /// <summary>
        /// Супервизор работы с виртуальным обордованием
        /// </summary>
        private ModellingSupervisor.ModellingSupervisor modellingSupervisor;

        /// <summary>
        /// Модуль для работы с виртуальным обордованием
        /// </summary>
        private ModellingModule.ModellingModule modellingModule;

        /// <summary>
        /// Диспетчер управляющих сборок
        /// </summary>
        private SupervisorDispatcher supervisorDispatcher;

        /// <summary>
        /// Диспетчер соединений
        /// </summary>
        private ConnectionDispatcher connectionDispatcher;

        /// <summary>
        /// Таблица пинов, на которую ссылается VMLua
        /// </summary>
        private VirtualPinTable vPinTable;

        /// <summary>
        /// Виртуальная машина Lua
        /// </summary>
        private IVirtualMachineService luaVM;

        /// <summary>
        /// Состояние исполнения
        /// </summary>
        private StateType stateF = StateType.Stop;

        /// <summary>
        /// Тип установки
        /// </summary>
        private BenchType benchType;

        /// <summary>
        /// Список акторов
        /// </summary>
        private List<ActiveObject> actors = new List<ActiveObject>();

        #endregion

        /// <summary>
        /// Состояние исполнения
        /// </summary>
        public StateType State
        {
            get { return stateF; }
            private set
            {
                #region Timers' tuning
                var oldState = State;
                stateF = value;
                switch (value)
                {
                    case StateType.Pause:
                        OnPause();
                        break;

                    case StateType.Run:
                        if (oldState == StateType.Stop)
                            OnRun();
                        else if (oldState == StateType.Pause)
                            OnContinue();
                        else throw new Exception("Error: a try to double Run.");
                        break;

                    case StateType.Stop:
                        if (oldState != StateType.Stop)
                            OnStop();
                        break;
                }
                #endregion
            }
        }

        /// <summary>
        /// Количество всего исполняемых акторов. Подсчет количества отработавших - единственный способ узнать, закончилось ли выполнение программы
        /// </summary>
        private int actor_all_count;

        /// <summary>
        /// Количество сейчас исполняемых акторов. Подсчет количества отработавших - единственный способ узнать, закончилось ли выполнение программы
        /// </summary>
        private int actor_count;

        /// <summary>
        /// Конструктор менеджера исполнения программы
        /// </summary>
        /// <param name="serviceProvider">провайдер сервисов ECO</param>
        /// <param name="loggingManagement">менеджмент логов</param>
        /// <param name="visualizationTarget">контрол, на котором производить визуализацию</param>
        /// <param name="appControl"></param>
        /// <remarks>
        /// 1. Регистрировать свой фильтр сообщений на клавиатуру
        /// 2. Установить ссылки на  DomainModel и  VisualDomainModel
        /// 3. Создать диспетчеров
        /// 4. Инициализировать Lua
        /// 5. Настроить визуализатор на контрол
        /// 6. Подготовить таймеры
        /// </remarks>
        public RunTimeManager(IEcoServiceProvider serviceProvider, ILoggingManagement loggingManagement, Control visualizationTarget, Control appControl)
        {
            this.serviceProvider = serviceProvider;
            this.oclService = serviceProvider.GetEcoService<IOclService>();
            this.visualizationTarget = visualizationTarget;
            this.appControl = appControl;
            this.loggingManagement = loggingManagement;
            logger = loggingManagement.GetLogger("RunTimeManagerLogger");

            // Регистрируем свой фильтр сообщений
            InitializeMessageFilter();

            //new VisualDMLoader(serviceProvider).LoadVisualDM(Settings.Default.EmptyVDMPath);
            domainModel = oclService.Evaluate("DomainModel.allInstances->first").AsObject as DomainModel;
            visualDomainModel = oclService.Evaluate("VisualDomainModel.allInstances->first").AsObject as VisualDomainModel;
            pinsDescriptionTable = oclService.Evaluate("PinsDescriptionTable.allInstances->first").AsObject as PinsDescriptionTable;

            // Создать диспетчер соединений и супервизоров
            connectionDispatcher = new ConnectionDispatcher();
            supervisorDispatcher = new SupervisorDispatcher();

            // Дать Lua ссылку на таблицу пинов и установить обработчик на завершение исполнения
            // Требуется: supervisorDispatcher, pinsDescriptionTable
            InitializeLuaVM(supervisorDispatcher, pinsDescriptionTable);

            if ((visualDomainModel != null) && (domainModel != null))
                // Инициализировать визуализацию, если она еще не инифиализирована
                // (требуется: visualDomainModel, visualizationTarget)
                InitializeVisualizationOnce();

            // Подготовить таймеры: системный таймер Lua и таймер для обновления на визуализации
            PrepareTimers();
        }

        /// <summary>
        /// Получить менеджер времени исполнения
        /// </summary>
        /// <remarks>
        /// Реализация паттерна проектирования "Одиночка" (Singleton)
        /// </remarks>   
        /// <param name="loggingManagement">менеджмент логов</param>
        /// <param name="serviceProvider">провайдер сервисов ECO</param>
        /// <param name="visualizationTarget">контрол, на котором производить визуализацию</param>
        public static RunTimeManager GetRunTimeManager(IEcoServiceProvider serviceProvider, ILoggingManagement loggingManagement, Control appControl)
        {
            if (manager == null)
                manager = new RunTimeManager(serviceProvider, loggingManagement, appControl, appControl);
            return manager;
        }

        #region Initialization


        /// <summary>
        /// Произвести необходимые действия при загрузке нового проекта
        /// </summary>
        public void OnProjectLoaded()
        {
            // Получить актуальные VisualDomainModel и DomainModel
            domainModel = oclService.Evaluate("DomainModel.allInstances->first").AsObject as DomainModel;
            visualDomainModel = oclService.Evaluate("VisualDomainModel.allInstances->first").AsObject as VisualDomainModel;
            pinsDescriptionTable = oclService.Evaluate("PinsDescriptionTable.allInstances->first").AsObject as PinsDescriptionTable;

            // Инициализировать визуализацию, если она еще не инифиализирована
            // (требуется: visualDomainModel, visualizationTarget)
            //InitializeVisualizationOnce();

            modellingSupervisor = new ModellingSupervisor.ModellingSupervisor(domainModel, visualDomainModel, vPinTable, loggingManagement);

            modellingModule = new ModellingModule.ModellingModule(serviceProvider);

            // Требуются: domainModel
            supervisorDispatcher.RemoveAll();
            supervisorDispatcher.AddSupervisor(new MockConnection("MockSupervisor"));
            supervisorDispatcher.AddSupervisorsRange(new ManagingAssembliesLoader(serviceProvider, logger, connectionDispatcher)
                                 .GetAssemblies(domainModel));
            supervisorDispatcher.AddSupervisor(modellingSupervisor);

            //регистрируем таблицу виртуальных пинов, для обращения к ним из Lua-кода
            luaVM.RegisterObject("vipins", vPinTable);

            serviceProvider.GetEcoService<ISystemTimer>()
                .Subscribe(new EventHandler(this.RunTimeManager_OnTick));
            luaVM.OnAllCoroutineDead += OnAllCoroutineDead;                        

            keyFilter.Subscribe(new KeyPressEventHandler(Visualizator.HandleKeyPress));
            Visualizator.ResetToInitPositions();
            Visualizator.RenderFrame();
            visualizationTimer.Enabled = true;
        }


        /// <summary>
        /// Произвести необходимые действия после выгрузки проекта
        /// </summary>
        public void OnProjectUnloaded()
        {
            if (Visualizator != null)
                keyFilter.DetachSubscription(Visualizator.HandleKeyPress);
            visualizationTimer.Enabled = false;

            domainModel = null;
            //visualDomainModel = null;
            if (modellingSupervisor != null)
            {
                modellingSupervisor.Dispose();
                modellingSupervisor = null;
            }

            if (modellingModule != null)
            {
                modellingModule.Dispose();
                modellingModule = null;
            }

            serviceProvider.GetEcoService<ISystemTimer>()
                .DetachSubcribtion(new EventHandler(this.RunTimeManager_OnTick));

            luaVM.OnAllCoroutineDead -= OnAllCoroutineDead;
        }

        /// <summary>
        /// При закрытии приложения
        /// </summary>
        public void OnExit()
        {
            DeInitializeConnections();
            Application.RemoveMessageFilter(keyFilter);
            DeinitializeVisualisation();
        }




        /// <summary>
        /// Выполнить запуск
        /// </summary>
        private void OnRun()
        {
            Visualizator.ResetToInitPositions();
            modellingSupervisor.Begin();
            luaVM.Begin();
            foreach (var module in modules_real)
            {                
                module.Initialize();                
            }
            serviceProvider.GetEcoService<ISystemTimer>().Enabled = true;
        }


        /// <summary>
        /// Пауза
        /// </summary>
        private void OnPause()
        {
            serviceProvider.GetEcoService<ISystemTimer>().Enabled = false;
        }

        /// <summary>
        /// Продолжить выполнение
        /// </summary>
        private void OnContinue()
        {
            serviceProvider.GetEcoService<ISystemTimer>().Enabled = true;
        }


        /// <summary>
        /// При вынужденной остановке выполнения скинуть все в начальное состояние
        /// </summary>
        private void OnStop()
        {
            serviceProvider.GetEcoService<ISystemTimer>().Enabled = false;
            luaVM.End();
            modellingSupervisor.End();

            connectionDispatcher.FlushPins();
            supervisorDispatcher.FlushPins();

            DeInitializeConnections();

            Visualizator.ResetToInitPositions();

            serviceProvider.GetEcoService<IFunctionManager>().ClearLibrary();

            Finished(RunFinshedState.Successfull);
        }


        /// <summary>
        /// Зарегестрировать свой фильтр сообщений, чтобы он их отлавливал
        /// </summary>
        /// <remarks>
        /// иначе Axiom все перехватывает
        /// </remarks>
        private void InitializeMessageFilter()
        {
            keyFilter = new KeyPressMessageFilter();
            Application.AddMessageFilter(keyFilter);
        }


        /// <summary>
        /// Инициализировать виртуальную машину Lua: дать ссылку на таблицу пинов и установить обработчик на завершение исполнения
        /// </summary>
        /// <param name="dispatcher">диспетчер управляющих сборок</param>
        /// <param name="pinTable">таблица всех виртуальный пинов</param>
        private void InitializeLuaVM(SupervisorDispatcher dispatcher, PinsDescriptionTable pinTable)
        {
            luaVM = serviceProvider.GetEcoService<IVirtualMachineService>();

            vPinTable = new CommonClasses.VirtualPinTable(dispatcher, pinTable, loggingManagement);

            //регистрируем таблицу виртуальных пинов, для обращения к ним из Lua-кода
            luaVM.RegisterObject("vipins", vPinTable);
        }


        /// <summary>
        /// Инициализировать визуализацию
        /// </summary>
        /// <remarks>
        /// требуется: visualDomainModel, visualizationTarget
        /// </remarks>
        private void InitializeVisualizationOnce()
        {
            if (!VisualisationInitislized)
            {
                PathUtility.RestoreCurrentDirectory(Locality.ExeFile);
                Visualizator = new Visualizator.Visualizator(serviceProvider, appControl, visualDomainModel);
                CameraSceneObject cam = new CameraSceneObject(serviceProvider) { Name = "Main_Camera", ID = 0x01 };
                cam.DefaultPosition.Vector = new Vector3(-35.57839f, 18.11339f, -37.26171f);
                cam.DefaultLookAt.Vector = new Vector3(-35.2943018f, 17.7366275f, -36.38005f);
                cam.DefaultPosition.Vector = new Vector3(27.2331f, 59.00272f, 114.2085f);
                cam.DefaultLookAt.Vector = new Vector3(26.6222929f, 58.528138f, 113.5747128f);

                DirectionalLightSceneObject l = new DirectionalLightSceneObject(serviceProvider) { Name = "Main_Light", ID = 0x02 };
                l.Color.Color = new ColorEx(0.5f, 0.5f, 0.5f);
                l.DefaultPosition.Vector = new Vector3(500, 500, 500);
                l.Direction.Vector = new Vector3(-1, -1, -1);
                l.Specular.Color = new ColorEx(0.859375f, 0.9843137f, 0f);
                l.Diffuse.Color = new ColorEx(0.4765625f, 0.43359375f, 0.38671875f);
                Visualizator.AddSceneObject(l);

                Visualizator.AddRenderWindow(visualizationTarget, cam);
                visualDomainModel.Initialize(Visualizator.sceneManager);
                Visualizator.StartRender();

                VisualisationInitislized = true;
            }
        }

        /// <summary>
        /// Деинициализировать визуализацию
        /// </summary>
        private void DeinitializeVisualisation()
        {
            if (Visualizator != null)
                Visualizator.FreeRenderContext();

            var cam = serviceProvider.GetEcoService<IOclService>().Evaluate("CameraSceneObject.allInstances->First").AsObject as CameraSceneObject;
            if (cam != null) cam.AsIObject().Delete();

            var light = serviceProvider.GetEcoService<IOclService>().Evaluate("DirectionalLightSceneObject.allInstances->First").AsObject as CameraSceneObject;
            if (light != null) light.AsIObject().Delete();
        }


        /// <summary>
        /// Установить подключение к установке (составить список подключений, т.е. модулей)
        /// </summary>
        /// <remarks>
        /// Требуется: порт, таймер
        /// Установленные соединения добавляются в connectionDispatcher
        /// </remarks>
        /// <param name="benchType">Тип установки</param>
        public void ConnectToBench(BenchType benchType)
        {
            connectionDispatcher.RemoveAllConnections();
            connectionDispatcher.AddConnection(new MockConnection("MockModule"));
            connectionDispatcher.AddConnection(modellingModule);
            modellingSupervisor.SetMode(benchType == BenchType.Virtual);

            if (benchType == BenchType.Real)
            {
                var pinsDescriptionTable = oclService.Evaluate("PinsDescriptionTable.allInstances->first").AsObject as PinsDescriptionTable;
                var timer = serviceProvider.GetEcoService<ISystemTimer>();

                try
                {
                    foreach (var KEetalon in pinsDescriptionTable.Etalons)
                    {
                        var KE = new KEUSBModule.KEUSBModule(pinsDescriptionTable, KEetalon.Descriptor);
                        modules_real.Add(KE);
                        timer.Subscribe(KE.OnTick);
                        connectionDispatcher.AddConnection(KE);
                    }

                    var stepperModule = new StepperModule.StepperModule(pinsDescriptionTable, 47);
                    modules_real.Add(stepperModule);
                    timer.Subscribe(stepperModule.OnTick);
                    connectionDispatcher.AddConnection(stepperModule);
                    /*
                    var gantryRobotFingersModule = new KEUSBModule.KEUSBModule(pinsDescriptionTable, );
                    modules.Add(KE);
                    timer.Subscribe(KE.OnTick);
                    connectionDispatcher.AddConnection(KE);
                     */
                    IsRealBenchConnected = true;
                }
                catch (KEUSB24AAbsentException kex)
                {
                    IsRealBenchConnected = false;
                    DeInitializeConnections();
                }
            }
        }


        /// <summary>
        /// Освободить подключение
        /// </summary>
        private void DeInitializeConnections()
        {
            var timer = serviceProvider.GetEcoService<ISystemTimer>();
            connectionDispatcher.RemoveAllConnections();
            foreach (var module in modules_real)
            {
                timer.DetachSubcribtion(module.OnTick);                
                module.Dispose();
            }
            modules_real.Clear();
        }


        /// <summary>
        /// Подготовить таймеры: системный таймер Lua и таймер визуализации
        /// </summary>
        private void PrepareTimers()
        {
            serviceProvider.GetEcoService<ISystemTimer>().Enabled = false;
            serviceProvider.GetEcoService<ISystemTimer>().Interval = SYS_TIMER_INTERVAL;

            visualizationTimer = new System.Windows.Forms.Timer();
            visualizationTimer.Enabled = false;
            visualizationTimer.Tick += new EventHandler(OnVisualizationTick);
            visualizationTimer.Interval = 40;
        }

        #endregion

        /// <summary>
        /// Обработчик системного таймера
        /// </summary>
        /// <param name="sender">object</param>
        /// <param name="e">EventArgs</param>
        private void RunTimeManager_OnTick(object sender, EventArgs e)
        {
            luaVM.ProcessOneStep();
            modellingSupervisor.OnPhisicModelling();
        }

        #region RunTimeManagerService members

        /// <summary>
        /// Исполнить программу с начала до конца
        /// </summary>
        /// <param name="benchType">тип установки</param>
        /// <param name="compilationData">данные компиляции</param>
        public void Run(BenchType benchType, List<CompilationData> compilationData)
        {
            this.benchType = benchType;

            ConnectToBench(benchType);
            // Установленные соединения в connectionDispatcher

            if ((!IsRealBenchConnected) && (benchType == BenchType.Real))
            {
                Finished(RunFinshedState.ErrorConnecting);
                return;
            }

            actor_all_count = compilationData.Count;
            actor_count = actor_all_count;
            foreach (CompilationData compData in compilationData)
            {
                var actor = new ActiveObject("CompData" + compilationData.IndexOf(compData).ToString());
                actor.LuaProgram = compData.CompiledCode;
                actor.ScriptOneStepCode = compData.CoroutineCallString;
                actor.OnDone += new EventHandler(actor_OnDone);
                luaVM.RegisterActiveObject(actor);
            }

            State = StateType.Run;
        }

        /// <summary>
        /// Количество модулей компиляции
        /// </summary>
        private int сUnitCount;

        /// <summary>
        /// Количество завершенных во время исполнения модулей компиляции
        /// </summary>
        private int cUnitEnded = 0;
       

        /// <summary>
        /// Событие на окончание работы актора
        /// </summary>
        /// <param name="sender">sender</param>
        /// <param name="e">EventArgs</param>
        private void actor_OnDone(object sender, EventArgs e)
        {
            actor_count--;
            Console.WriteLine("actor " + (sender as ActiveObject).Name + " is done");
            if (actor_count == 0)
                Stop();
        }

        /// <summary>
        /// Принужденно остановить выполнение и скинуть все в начальное состояние
        /// </summary>
        public void Stop()
        {
            State = StateType.Stop;
        }

        /// <summary>
        /// Приостановить исполнение, если оно идет, и продолжить начатое, если его не было
        /// </summary>
        public void PlayPause()
        {
            if (State == StateType.Run)
                State = StateType.Pause;
            else State = StateType.Run;
        }

        #endregion

        #region RunTimeManagers' Event handlers

        /// <summary>
        /// Событие после уничтожения всех сопроцедур (т.е. окончание исполения)
        /// </summary>
        private void OnAllCoroutineDead()
        {
            Stop();
            /*if (benchType == BenchType.Real)
                MessageBox.Show("Исполнение на установке - Программа завершена.");
            else
                MessageBox.Show("Моделирование - Программа завершена.");
             */
        }


        /// <summary>
        /// Тик таймера визуализатора
        /// </summary>
        /// <remarks>
        /// обновим изображение
        /// </remarks>
        /// <param name="sender">sender</param>
        /// <param name="e">EventArgs</param>
        private void OnVisualizationTick(object sender, EventArgs e)
        {
            Visualizator.RenderFrame();
        }

        #endregion
    }

    /* // для отладки Lua-кода
    public class ConsoleWriter
    {
        public void Write(string s)
        {
            Console.WriteLine(s);
        }
    }
     */

}

